<?php
/**
 * @author Indrek Altpere
 * @copyright Indrek Altpere
 * @uses Mysql package released by me
 * @see ErrorManager for convenient error logging
 *
 * Proveds means of more convenient mysql usage by making it possible to call the functions as static functions and thus removing the need of passing the mysql connection variable around.
 * Static wrapper class for DynMysql class, can contain only connection to one database, for using multiple databases simultaneously, use DynMysql class
 *
 */
class Mysql {
	/**
	 * Dynamic mysql stuff
	 *
	 * @var DynMysql
	 */
	private static $mysql;
	private static $inited = false;

	/**
	 * Disables instantiating of this class
	 *
	 */
	private function __construct() {
	}

	/**
	 * Returns mysql escaped string
	 *
	 * @param string $str String to escape
	 * @return string Escaped string
	 */
	public static function EscapeString($str) {
		return self::$mysql->EscapeString($str);
	}

	/**
	 * Initializes the static Mysql class variables
	 *
	 * @param string $db_host Hostname
	 * @param string $db_user Username
	 * @param string $db_pass Password
	 * @param string $db_name Database name
	 * @param boolean $autoconnect Whether to initialize connection right away or when first query is made
	 * @param boolean $persistent Whether to use persisten connection
	 */
	public static function Init($db_host, $db_user, $db_pass , $db_name, $autoconnect = false, $persistent = false) {
		if(self::$inited) {
			return;
		}
		self::$mysql = new DynMysql($db_host, $db_user, $db_pass, $db_name, $autoconnect, $persistent);
		self::$inited = true;
	}

	/**
	 * Performs query on database
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return mysql_result Mysql result
	 */
	public static function &Query($cmd) {
		return self::$mysql->Query($cmd);
	}

	/**
	 * Returns how many rows were found in last query
	 * Note: last query must include SQL_CALC_FOUND_ROWS for thist to work as meant to
	 * It is meant to take away the 2 query step where you first select the count of rows according to WHERE statement (to know the total corresponding rows),
	 * after which you select the actual rows itself with limit statement to display data on multiple pages for example.
	 * SQL_CALC_FOUND_ROWS tells mysql to remember the count of total found rows even if you used LIMIT statement to get only partial result of all matches.
	 *
	 * @return int Count of found rows
	 */
	public static function FoundRows(){
		return self::$mysql->FoundRows();
	}

	/**
	 * Returns single mysql result row as unassociated array of selected field values (array keys are integers, not fieldnames) on success, false otherwise
	 * array('val1', 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of field values or false if query was unsuccessful
	 */
	public static function &GetRow($cmd) {
		return self::$mysql->GetRow($cmd);
	}

	/**
	 * Returns multiple mysql result rows as array of unassociated arrays of selected field values (array keys are integers, not fieldnames), empty array if unsuccessful
	 * array(array('val1', 'val2'), array('val3', 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $bundle Whether to bundle first values of result rows into one single level array
	 * @return array Array of results
	 */
	public static function &GetRows($cmd, $bundle = false) {
		return self::$mysql->GetRows($cmd, $bundle);
	}

	/**
	 * Returns multiple mysql result rows as array of associated arrays of selected field values (array keys are fieldnames), empty array in unsuccessful
	 * array(array('name1' => 'val1', 'name2' => 'val2'), array('name1' => 'val3', 'name2' => 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $assignid Whether to return arrays so that the id field values are set to be the keys of the arrays ( idvalue => array(fieldname=>fieldvalue) )
	 * @param boolean $bundle Whether to bundle all subarrays into one single array using the first subarray value (good to select id fields and instead of looping with nested foreaches or using $row['id'], just get the values as one array)
	 * @return array Array of results
	 */
	public static function &GetArrays($cmd, $assignid = false, $bundle = false) {
		return self::$mysql->GetArrays($cmd, $assignid, $bundle);
	}

	/**
	 * Returns single mysql result rows as associated array of selected field values (array keys are fieldnames), false in unsuccessful
	 * array('name1' => 'val1', 'name2' => 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of fielname => fieldvalue mappings or false if unsuccessful
	 */
	public static function &GetArray($cmd) {
		return self::$mysql->GetArray($cmd);
	}

	/**
	 * Returns the id of the last row inserted into database
	 *
	 * @return int Id of last inserted row
	 */
	public static function &InsertId() {
		return self::$mysql->InsertId();
	}

	/**
	 * Returns first column value of first selected row
	 *
	 * @param string $cmd Mysql query to make
	 * @return string
	 */
	public static function &GetSingleRowField($cmd) {
		return self::$mysql->GetSingleRowField($cmd);
	}

	/**
	 * Sets the Mysql class debug mode (in debug mode, queryes and their related values are stored and can be viewed by calling ToString method)
	 *
	 * @param boolean $debug
	 */
	public static function SetDebug($debug = false) {
		self::$mysql->SetDebug($debug);
	}

	/**
	 * Returns data about executed mysql queries in string form, to get more detailed data about each queries(spent time, affected rows etc), use SetDebug(true) before making any queries
	 *
	 * @return string
	 */
	public static function ToString() {
		return self::$mysql->ToString();
	}

	/**
	 * Starts transaction
	 *
	 * @return boolean If transaction was started successfully
	 */
	public static function TransactionBegin() {
		return self::$mysql->TransactionBegin();
	}

	/**
	 * Ends/commits transaction
	 *
	 * @return boolean If commiting was successful
	 */
	public static function TransactionEnd() {
		return self::$mysql->TransactionEnd();
	}

	/**
	 * Rolls back current transaction
	 *
	 * @return boolean If rolling back was successful
	 */
	public static function TransactionRollback() {
		return self::$mysql->TransactionRollback();
	}

	/**
	 * Retrieves iterator class for result
	 *
	 * @param string $query
	 * @return MysqlIterator
	 */
	public static function &GetIterator($query) {
		return self::$mysql->GetIterator($query);
	}

	/**
	 * Gets full column data description for wanted table as associative array with keys:
	 * Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment
	 *
	 * @param string $tablename Name of the table for what to get the column data
	 * @return array Array of
	 */
	public static function GetColumnDataForTable($tablename) {
		return self::$mysql->GetColumnDataForTable($tablename);
	}

	/**
	 * Gets the results of table expain query as associative array with keys:
	 * Field, Type, Null, Key, Default, Extra
	 *
	 * @param unknown_type $tablename
	 */
	public static function GetExplainTable($tablename) {
		return self::$mysql->GetExplainTable($tablename);
	}

	/**
	 * Builds Order by statement from passed in array(fieldname => order)
	 *
	 * @param array $fieldorderarr
	 * @return string
	 */
	public static function &BuildOrderByStatement($fieldorderarr) {
		return self::$mysql->BuildOrderByStatement($fieldorderarr);
	}

	/**
	 * Builds Limit statement from passed in variables
	 *
	 * @param int $start Start of limit array
	 * @param int $count Count of rows to select
	 * @return string
	 */
	public static function &BuildLimitStatement($start, $count) {
		return self::$mysql->BuildLimitStatement($start, $count);
	}

	/**
	 * Builds Set statement from passed in array
	 *
	 * @param array $array
	 * @return string Set statement
	 */
	public static function BuildSetStatement(&$array) {
		return self::$mysql->BuildSetStatement($array);
	}

	/**
	 * Builds Insert statement for given table using given fields and values
	 *
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @return string Statement for inserting data into table
	 */
	public static function BuildInsertStatement($tablename, &$array) {
		return self::$mysql->BuildInsertStatement($tablename, $array);
	}

	/**
	 * Builds Update statement for given table using given fields and values
	 *
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @param int $id Id of the row to update
	 * @return string Statement for updating a row data in table
	 */
	public static function BuildUpdateStatement($tablename, &$array, $id) {
		return self::$mysql->BuildUpdateStatement($tablename, $array, $id);
	}

	/**
	 * Builds Delete statement for given table using given id
	 *
	 * @param string $tablename Name of the table
	 * @param int $id Id of the row to delete
	 * @return string Statement for deleting a row from table
	 */
	public static function BuildDeleteStatement($tablename, $id) {
		return self::$mysql->BuildDeleteStatement($tablename, $id);
	}

	/**
	 * Truncates table
	 *
	 * @param string $tablename Name of the table
	 * @return boolean If truncating was successful
	 */
	public static function TruncateTable($tablename) {
		return self::$mysql->TruncateTable($tablename);
	}

	/**
	 * Retrieves table list from database
	 *
	 * @return array Array of table names in current database
	 */
	public static function &GetTables() {
		return self::$mysql->GetTables();
	}

	/**
	 * Retrieves list of fields from given table
	 *
	 * @param string $tablename
	 * @return array
	 */
	public static function &GetTableFields($tablename) {
		return self::$mysql->GetTableFields($tablename);
	}

	/**
	 * Returns time spent on last query
	 *
	 * @return float
	 */
	public static function TimeSpent() {
		return self::$mysql->TimeSpent();
	}

	/**
	 * Returns time spent on all queries together
	 *
	 * @return float
	 */
	public static function TimeSpentTotal() {
		return self::$mysql->TimeSpentTotal();
	}

	/**
	 * Returns how many rows were selected in last queriy
	 *
	 * @return int
	 */
	public static function SelectedRows() {
		return self::$mysql->SelectedRows();
	}

	/**
	 * Returns how many total rows were selected in all queries together
	 *
	 * @return unknown
	 */
	public static function SelectedRowsTotal() {
		return self::$mysql->SelectedRowsTotal();
	}

	/**
	 * Returns how many rows were affected by last query
	 *
	 * @return int
	 */
	public static function AffectedRows() {
		return self::$mysql->AffectedRows();
	}

	/**
	 * Returns how many total rows were affected in all queries together
	 *
	 * @return int
	 */
	public static function AffectedRowsTotal() {
		return self::$mysql->AffectedRowsTotal();
	}

	/**
	 * Returns if there is a transaction active currently
	 *
	 * @return boolean
	 */
	public static function InTransaction() {
		return self::$mysql->InTransaction();
	}
}

/**
 * Class for mysql stuff, each instance can be connected to any database
 *
 * Supports delayed connecting: if you instantiate class, it does not bring up the mysql connection unless autoconnect is set to true
 * If autoconnect was set to false, first query made triggers the connection creation
 * Meaning if site does not need mysql queries to be done, the mysql connection is not brought up and therefore page loading takes less time
 *
 */
final class DynMysql {
	private $res = null;
	private $queries = 0;
	private $db = null;
	private $time_spent_total = 0;
	private $affected_rows_total = 0;
	private $selected_rows_total = 0;
	private $time_spent = 0;
	private $found_rows = 0;
	private $affected_rows = 0;
	private $selected_rows = 0;
	private $conndata = array('db_host' => '', 'db_user' => '', 'db_pass' => '' , 'db_name' => '');
	private $persistent = false;
	private $inited = false;
	private $queryarr = array();
	private $debug = false;
	private $in_transaction = false;

	/**
	 * Initializes the DynMysql class variables
	 *
	 * @param string $db_host Hostname
	 * @param string $db_user Username
	 * @param string $db_pass Password
	 * @param string $db_name Database name
	 * @param boolean $autoconnect Whether to initialize connection right away or when first query is made
	 * @param boolean $persistent Whether to use persisten connection or not
	 */
	public function __construct($db_host, $db_user, $db_pass , $db_name, $autoconnect = false, $persistent = false) {
		$this->conndata = array('db_host' => $db_host, 'db_user' => $db_user, 'db_pass' => $db_pass , 'db_name' => $db_name);
		$this->persistent = $persistent;
		if($autoconnect) {
			$this->Connect();
		}
	}

	/**
	 * Destructor, closes open connections
	 *
	 */
	public function __destruct() {
		$this->Close();
	}

	/**
	 * Closes existing connection
	 *
	 */
	private function Close() {
		if($this->db) {
			mysql_close($this->db);
			$this->db = null;
		}
	}

	/**
	 * Returns mysql escaped string
	 *
	 * @param string $str String to escape
	 * @return string Escaped string
	 */
	public function EscapeString($str) {
		//init db conn if needed
		if(is_null($this->db)) {
			$this->Connect();
			if(is_null($this->db)) {
				trigger_error('Mysql error: No connection to database!?!?', E_USER_ERROR);
				return null;
			}
		}
		return mysql_real_escape_string($str, $this->db);
	}

	private function Connect() {
		if($this->db) {
			return;
		}
		$fname = 'mysql_' . ($this->persistent ? 'p' : '') . 'connect';
		$conn = &$this->conndata;
		$this->db = $fname($conn['db_host'], $conn['db_user'], $conn['db_pass']);
		mysql_select_db($conn['db_name'], $this->db);
	}

	/**
	 * Performs query on database
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return mysql_result Mysql result
	 */
	public function &Query($cmd, $addDebugData = true) {
		if($addDebugData) $q_start = self::GetMicroTime();
		if(is_null($this->db)) {
			$this->Connect();
			if(is_null($this->db)) {
				trigger_error('Mysql error: No connection to database!?!?'." ($cmd)", E_USER_ERROR);
				return null;
			}
		}
		$this->queries += 1;
		$this->res = mysql_query($cmd, $this->db);
		$errno = mysql_errno($this->db);
		$err = mysql_error($this->db);
		if($errno) {
			trigger_error('Mysql error '.$errno.': '.$err." ($cmd)", E_USER_ERROR);
		}
		$this->affected_rows = @mysql_affected_rows($this->db);
		$this->affected_rows_total += $this->affected_rows;
		$this->selected_rows = is_resource($this->res) ? @mysql_num_rows($this->res) : 0;
		$this->selected_rows_total += $this->selected_rows;
		if($addDebugData) {
			$q_end = self::GetMicroTime();
			$this->time_spent = $q_end - $q_start;
			$this->time_spent_total += $this->time_spent;
			$this->AddDebugData($cmd);
		}
		return $this->res;
	}

	/**
	 * Returns how many rows were found in last query
	 * Note: last query must include SQL_CALC_FOUND_ROWS for thist to work as meant to
	 * It is meant to take away the 2 query step where you first select the count of rows according to WHERE statement (to know the total corresponding rows),
	 * after which you select the actual rows itself with limit statement to display data on multiple pages for example.
	 * SQL_CALC_FOUND_ROWS tells mysql to remember the count of total found rows even if you used LIMIT statement to get only partial result of all matches.
	 *
	 * @return int Count of found rows
	 */
	public function FoundRows(){
		$buf = $this->GetRow('SELECT FOUND_ROWS()');
		return $buf[0];
	}

	/**
	 * Returns single mysql result row as unassociated array of selected field values (array keys are integers, not fieldnames) on success, false otherwise
	 * array('val1', 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of field values or false if query was unsuccessful
	 */
	public function &GetRow($cmd) {
		$q_start = self::GetMicroTime();
		if($this->Query($cmd, false)) $buf = mysql_fetch_row($this->res);
		else $buf = false;
		$q_end = self::GetMicroTime();
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $buf;
	}

	/**
	 * Returns multiple mysql result rows as array of unassociated arrays of selected field values (array keys are integers, not fieldnames), empty array if unsuccessful
	 * array(array('val1', 'val2'), array('val3', 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $bundle Whether to bundle first values of result rows into one single level array
	 * @return array Array of results
	 */
	public function &GetRows($cmd, $bundle = false) {
		$q_start = self::GetMicroTime();
		$m = array();
		if($this->Query($cmd, false)) {
			while(($t = mysql_fetch_row($this->res))) {
				if($bundle) {
					$m[] = reset($t);
				} else {
					$m[] = $t;
				}
			}
		}
		$q_end = self::GetMicroTime();
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $m;
	}

	/**
	 * Returns multiple mysql result rows as array of associated arrays of selected field values (array keys are fieldnames), empty array in unsuccessful
	 * array(array('name1' => 'val1', 'name2' => 'val2'), array('name1' => 'val3', 'name2' => 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $assignid Whether to return arrays so that the id field values are set to be the keys of the arrays ( idvalue => array(fieldname=>fieldvalue) )
	 * @param boolean $bundle Whether to bundle all subarrays into one single array using the first subarray value (good to select id fields and instead of looping with nested foreaches or using $row['id'], just get the values as one array)
	 * @return array Array of results
	 */
	public function &GetArrays($cmd, $assignid = false, $bundle = false) {
		$q_start = self::GetMicroTime();
		$m = array();
		if($this->Query($cmd, false)) {
			while(($t = mysql_fetch_array($this->res, MYSQL_ASSOC))) {
				if($assignid && isset($t['id'])) {
					$m[$t['id']] = $bundle ? next($t) : $t;
				} elseif ($bundle) {
					$m[] = reset($t);
				} else {
					$m[] = $t;
				}
			}
		}
		$q_end = self::GetMicroTime();
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $m;
	}

	/**
	 * Returns single mysql result rows as associated array of selected field values (array keys are fieldnames), false in unsuccessful
	 * array('name1' => 'val1', 'name2' => 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of fielname => fieldvalue mappings or false if unsuccessful
	 */
	public function &GetArray($cmd) {
		$q_start = self::GetMicroTime();
		if($this->Query($cmd, false)) {
			$buf = mysql_fetch_array($this->res, MYSQL_ASSOC);
		} else $buf = false;
		$q_end = self::GetMicroTime();
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $buf;
	}

	/**
	 * Returns first column value of first selected row
	 *
	 * @param string $cmd Mysql query to make
	 * @return string
	 */
	public function &GetSingleRowField($cmd) {
		$row = $this->GetRow($cmd);
		if(is_array($row)) {
			return reset($row);
		}
		return false;
	}

	/**
	 * Returns the id of the last row inserted into database
	 *
	 * @return int Id of last inserted row
	 */
	public function &InsertId() {
		$id = mysql_insert_id($this->db);
		$errno = mysql_errno($this->db);
		$err = mysql_error($this->db);
		if($errno) {
			trigger_error('Mysql error '.$errno.': '.$err." (getting inserted id)", E_USER_ERROR);
		}
		return $id;
	}

	/**
	 * Starts transaction
	 *
	 * @return boolean If transaction was started successfully
	 */
	public function TransactionBegin() {
		if($this->in_transaction) {
			return false;
		}
		$res = $this->Query('START TRANSACTION');
		if(!$res) {
			return false;
		}
		$this->in_transaction = true;
		return true;
	}

	/**
	 * Ends/commits transaction
	 *
	 * @return boolean If commiting was successful
	 */
	public function TransactionEnd() {
		if(!$this->in_transaction) {
			return false;
		}
		$this->in_transaction = false;
		$res = $this->Query('COMMIT');
		if(!$res) {
			return false;
		}
		return true;
	}

	/**
	 * Rolls back current transaction
	 *
	 * @return boolean If rolling back was successful
	 */
	public function TransactionRollback() {
		if(!$this->in_transaction) {
			return false;
		}
		$this->in_transaction = false;
		$res = $this->Query('ROLLBACK');
		if(!$res) {
			return false;
		}
		return true;
	}

	/**
	 * Gets full column data description for wanted table as associative array with keys:
	 * Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment
	 *
	 * @param string $tablename Name of the table for what to get the column data
	 * @return array Array of
	 */
	public function GetColumnDataForTable($tablename) {
		return $this->GetArrays('SHOW FULL COLUMNS FROM `'.$this->EscapeString($tablename).'`');
	}

	/**
	 * Gets the results of table expain query as associative array with keys:
	 * Field, Type, Null, Key, Default, Extra
	 *
	 * @param unknown_type $tablename
	 */
	public function GetExplainTable($tablename) {
		return $this->GetArrays('EXPLAIN `'.$this->EscapeString($tablename).'`');
	}

	/**
	 * Builds Order by statement from passed in array(fieldname => order)
	 *
	 * @param array $fieldorderarr
	 * @return string
	 */
	public function &BuildOrderByStatement($fieldorderarr) {
		if(!is_array($fieldorderarr) || !count($fieldorderarr)) {
			return '';
		}
		$allowedorders = array('ASC', 'DESC');
		$newarr = array();
		foreach ($fieldorderarr as $field => $order) {
			$order = strtoupper($order);
			if(!in_array($order, $allowedorders, true)) $order = 'ASC';
			$newarr[] = '`'.$this->EscapeString($field).'` '.$order;
		}
		return 'ORDER BY '.join(',', $newarr);
	}

	/**
	 * Builds Limit statement from passed in variables
	 *
	 * @param int $start Start of limit array
	 * @param int $count Count of rows to select
	 * @return string
	 */
	public function &BuildLimitStatement($start, $count) {
		$start = max(0, intval($start));
		$count = abs(intval($count));
		return 'LIMIT '.$start.','.$count;
	}

	/**
	 * Builds Set statement from passed in array
	 *
	 * @param array $array
	 * @return string Set statement
	 */
	public function &BuildSetStatement(&$array) {
		if(!count($array)) return '';
		$str = 'SET ';
		$strarr = array();
		foreach ($array as $k => &$v) {
			//if field is null, set database value to NULL also, otherwise escape it and put between "" since mysql does its own conversion anyways ("2" => 2)
			$strarr[] = '`'.$this->EscapeString($k).'`='.(is_null($v) ? 'NULL' : '"'.$this->EscapeString($v).'"');
		}
		return $str.join(',', $strarr);
	}

	/**
	 * Builds Insert statement for given table using given fields and values
	 *
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @return string Statement for inserting data into table
	 */
	public function BuildInsertStatement($tablename, &$array) {
		return 'INSERT INTO `'.$this->EscapeString($tablename).'` '.$this->BuildSetStatement($array);
	}

	/**
	 * Builds Update statement for given table using given fields and values
	 *
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @param int $id Id of the row to update
	 * @return string Statement for updating a row data in table
	 */
	public function BuildUpdateStatement($tablename, &$array, $id) {
		return 'UPDATE '.$this->EscapeString($tablename).' '.$this->BuildSetStatement($array).' WHERE `id`='.intval($id);
	}

	/**
	 * Builds Delete statement for given table using given id
	 *
	 * @param string $tablename Name of the table
	 * @param int $id Id of the row to delete
	 * @return string Statement for deleting a row from table
	 */
	public function BuildDeleteStatement($tablename, $id) {
		return 'DELETE FROM `'.$this->EscapeString($tablename).'` WHERE `id`='.intval($id);
	}

	/**
	 * Truncates table
	 *
	 * @param string $tablename Name of the table
	 * @return boolean If truncating was successful
	 */
	public function TruncateTable($tablename) {
		$res = $this->Query('TRUNCATE TABLE `'.$this->EscapeString($tablename).'`');
		return $res ? true : false;
	}

	/**
	 * Retrieves table list from database
	 *
	 * @return array Array of table names in current database
	 */
	public function &GetTables() {
		return $this->GetRows('SHOW TABLES', true);
	}

	/**
	 * Retrieves list of fields from given table
	 *
	 * @param string $tablename
	 * @return array
	 */
	public function &GetTableFields($tablename) {
		return $this->GetRows('EXPLAIN `'.$this->EscapeString($tablename).'`', true);
	}

	/**
	 * Retrieves iterator class for result
	 *
	 * @param string $query
	 * @return MysqlIterator
	 */
	public function &GetIterator($query) {
		return new MysqlIterator($query);
	}

	/**
	 * Returns time spent on last query
	 *
	 * @return float
	 */
	public function TimeSpent() {
		return $this->time_spent;
	}

	/**
	 * Returns time spent on all queries together
	 *
	 * @return float
	 */
	public function TimeSpentTotal() {
		return $this->time_spent_total;
	}

	/**
	 * Returns how many rows were selected in last queriy
	 *
	 * @return int
	 */
	public function SelectedRows() {
		return $this->selected_rows;
	}

	/**
	 * Returns how many total rows were selected in all queries together
	 *
	 * @return unknown
	 */
	public function SelectedRowsTotal() {
		return $this->selected_rows_total;
	}

	/**
	 * Returns how many rows were affected by last query
	 *
	 * @return int
	 */
	public function AffectedRows() {
		return $this->affected_rows;
	}

	/**
	 * Returns how many total rows were affected in all queries together
	 *
	 * @return int
	 */
	public function AffectedRowsTotal() {
		return $this->affected_rows_total;
	}

	/**
	 * Returns if there is a transaction active currently
	 *
	 * @return boolean
	 */
	public function InTransaction() {
		return $this->in_transaction;
	}

	/**
	 * Adds query to debugging array using the time_spent, affected_rows and selected_rows private variables set by last query
	 *
	 * @param string $query Mysql query that was run
	 */
	private function AddDebugData($query) {
		if($this->debug) {
			$this->queryarr[] = array('query' => $query, 'time_spent' => $this->time_spent, 'affected_rows' => $this->affected_rows, 'selected_rows' => $this->selected_rows);
		}
	}

	/**
	 * Sets the Mysql class debug mode (in debug mode, queryes and their related values are stored and can be viewed by calling ToString method)
	 *
	 * @param boolean $debug
	 */
	public function SetDebug($debug = false) {
		$this->debug = $debug ? true : false;
	}

	/**
	 * Returns data about executed mysql queries in string form, to get more detailed data about each queries(spent time, affected rows etc), use SetDebug(true) before making any queries
	 *
	 * @return string
	 */
	public function ToString() {
		$qstr = 'not available, set debug to true to see more data';
		if($this->debug) {
			$qstr = "\r\n" . self::ArrToString($this->queryarr) . "\r\n";
		}
		return sprintf("\t(mysql: queries:%s\t time_spent_total:%.08f\t sel_rows_total:%s\t aff_rows_total:%s\t queries: %s)", $this->queries, $this->time_spent_total, $this->selected_rows_total, $this->affected_rows_total, $qstr);
	}

	/**
	 * Converts an array to string similar to print_r but instead of outputting it directly, it is returned as a function result
	 *
	 * @param array $arr Array to convert to string representation
	 * @param int $level Reperesents the depth of recursion
	 * @return string String representation of array
	 */
	private static function ArrToString(&$arr, $level = 0) {
		$str = '';
		$pad = '';
		for($i = 0; $i < $level; $i++) $pad .= '  ';
		if(is_array($arr)) {
			$str .= "Array(\r\n";
			foreach ($arr as $k => $v) {
				$str .= $pad . '  [' . $k . '] => ' . self::ArrToString($v, $level + 1);
			}
			$str .= "$pad)\r\n";
		} else {
			return $arr."\r\n";
		}
		return $str;
	}

	/**
	 * Gets current time in unix format with high precision
	 *
	 * @return float Current time in unix format with high precision
	 */
	private static function GetMicroTime() {
		list($a,$b) = explode(' ',microtime());
		return (float)$a+(float)$b;
	}

	/**
	 * Frees up mysql resultset
	 *
	 */
	private function FreeResult() {
		if(is_resource($this->res)) {
			mysql_free_result($this->res);
		}
	}
}

class MysqlIterator implements SeekableIterator, Countable {
	private $mysqlResult = null;
	private $currentRow = null;
	private $index = 0;
	private $count = 0;
	private $query = null;
	public function __construct($result) {
		//if query string, exequte query and store result
		if(is_string($result)) {
			$this->query = $result;
			$result = Mysql::Query($result);
		}
		$this->mysqlResult = $result;
		$this->count = mysql_num_rows($result);
		$this->index = 0;
		$this->currentRow = null;
	}
	public function seek($index) {
		 $this->index = $index;
		return mysql_data_seek($this->mysqlResult, $index);
	}
	public function &next() {
		$this->currentRow = mysql_fetch_array($this->mysqlResult, MYSQL_ASSOC);
		$this->index += 1;
		return $this->currentRow;
	}
	public function &current() {
		return $this->currentRow;
	}
	public function valid() {
		return $this->index < $this->count;
	}
	public function rewind() {
		mysql_data_seek($this->mysqlResult, 0);
		$this->currentRow = $this->next();
		$this->index = 0;
	}
	public function key() {
		return $this->index;
	}
	public function count() {
		return $this->count;
	}
	public function __destruct() {
		if(is_resource($this->mysqlResult)) {
			mysql_free_result($this->mysqlResult);
			$this->mysqlResult = null;
		}
	}
	public function __sleep() {
		$this->__destruct();
	}
	public function __wakeup() {
		if($this->query) {
			$this->mysqlResult = Mysql::Query($this->query);
			$this->count = mysql_num_rows($this->mysqlResult);
		}
		$old = $this->index;
		$this->seek($old);
		$this->currentObj = $this->next();
		$this->seek($old);
	}

}

?>